package main

import (
	"context"
	"encoding/json"
	"errors"
	"flag"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/afex/hystrix-go/hystrix"
	"github.com/go-kit/kit/circuitbreaker"
	"github.com/go-kit/kit/endpoint"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/sd"
	"github.com/go-kit/kit/sd/etcdv3"
	"github.com/go-kit/kit/sd/lb"
)

var (
	srdETCDServers                                              = flag.String("srd-etcd-servers", "127.0.0.1:2379", "Comma-separated ETCD server addresses for Service Discovery & Regist. Example: '127.0.0.1:2379,127.0.0.1:13279'")
	srdETCDCaCert                                               = flag.String("srd-etcd-ca-cert", "", "ETCD client access root cert file path for Service Discovery & Registry")
	srdETCDCert                                                 = flag.String("srd-etcd-cert", "", "ETCD client access cert file path for Service Discovery & Registry")
	srdETCDKey                                                  = flag.String("srd-etcd-key", "", "ETCD client access cert's key file path for Service Discovery & Registry")
	srdPrefix                                                   = flag.String("srd-prefix", "/svc/", "Service Discovery & Registry Prefix")
	logger                                                      log.Logger
	paramLang, paramTarget, paramFunc, paramSvc, paramSessionID string
)

func init() {
	logger = log.NewJSONLogger(os.Stderr)
	logger = log.With(logger, "ts", log.DefaultTimestampUTC)
	logger = log.With(logger, "caller", log.DefaultCaller)

	flag.StringVar(&paramSvc, "paramSvc", "Say", "Service Parameter")
	flag.StringVar(&paramFunc, "paramFunc", "SayHello", "Function Parameter")
	flag.StringVar(&paramLang, "paramLang", "zh", "Lang Parameter")
	flag.StringVar(&paramTarget, "paramTarget", "世界", "Target Parameter")
	flag.StringVar(&paramSessionID, "paramSessionID", "demoSessionID", "Session ID")
}

func main() {
	flag.Parse()

	client, err := etcdv3.NewClient(context.Background(), strings.Split(*srdETCDServers, ","), etcdv3.ClientOptions{
		DialTimeout:   time.Second * 3,
		DialKeepAlive: time.Second * 3,
		CACert:        *srdETCDCaCert,
		Cert:          *srdETCDCert,
		Key:           *srdETCDKey,
	})
	if err != nil {
		logger.Log("Service regist to ETCD server fail", err)
		os.Exit(1)
	}

	//创建实例例管理理器器, 此管理理器器会Watch监听etc中prefix的⽬目录变化更更新缓存的服务实例例数据
	instancer, err := etcdv3.NewInstancer(client, *srdPrefix+paramSvc+"/HTTP/", logger)
	if err != nil {
		logger.Log("Service watcher on ETCD server create fail", err)
		os.Exit(1)
	}
	//创建端点管理理器器， 此管理理器器根据Factory和监听的到实例例创建endPoint并订阅instancer的变化
	endpointer := sd.NewEndpointer(instancer, reqFactory, logger)
	//创建负载均衡器器
	balancer := lb.NewRoundRobin(endpointer)
	reqEndPoint := lb.Retry(2, 3*time.Second, balancer)
	//创建服务熔断
	// 可通过关闭服务端予以测试
	commandName := "my-endpoint"
	hystrix.ConfigureCommand(commandName, hystrix.CommandConfig{
		Timeout:                1000,  // 请求超时的时间
		ErrorPercentThreshold:  50,    // 允许出现的错误比例
		SleepWindow:            10000, // 熔断开启多久尝试发起一次请求
		MaxConcurrentRequests:  1000,  // 允许的最大并发请求数
		RequestVolumeThreshold: 5,     // 波动期内的最小请求数，默认波动期 10S
	})
	reqEndPoint = circuitbreaker.Hystrix(commandName)(reqEndPoint)
	//通过 endPoint 发起请求
	req := struct{}{}
	t := time.NewTicker(time.Millisecond * 40)
	for range t.C {
		res, err := reqEndPoint(context.Background(), req)
		if err != nil {
			logger.Log("Service discovery from ETCD server fail", err)
		}
		logger.Log("Service call result", res)
	}
}

func reqFactory(instanceAddr string) (endpoint.Endpoint, io.Closer, error) {
	return func(ctx context.Context, request interface{}) (interface{}, error) {
		logger.Log("http call addr", instanceAddr)

		rs := map[string]interface{}{} // 定义http.Response.Body的结构对象

		client := &http.Client{}

		switch paramFunc {
		case "SayHello":
			reqBody, _ := json.Marshal(&JSONRequestBody{
				Lang:   paramLang,
				Target: paramTarget,
			})
			req, err := http.NewRequest("POST", instanceAddr+"/say/hello", strings.NewReader(string(reqBody)))
			if err != nil {
				logger.Log("HTTP request object create fail", err)
				return nil, err
			}

			req.Header.Set("Content-Type", "application/json")
			req.Header.Set("Sessionid", paramSessionID) // 追加sessionid到HTTP的Header信息中，用于服务链路追踪

			resp, err := client.Do(req)
			defer resp.Body.Close()
			if err != nil {
				logger.Log("HTTP request execute fail", err)
				return nil, err
			}

			resBody, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				logger.Log("HTTP response body read fail", err)
				return nil, err
			}
			json.Unmarshal(resBody, &rs)
		case "SayGoodby":
			req, err := http.NewRequest("GET", instanceAddr+"/say/goodby/"+paramLang+"?target="+paramTarget, strings.NewReader(""))
			if err != nil {
				logger.Log("HTTP request object create fail", err)
				return nil, err
			}

			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
			req.Header.Set("Sessionid", paramSessionID)

			resp, err := client.Do(req)
			defer resp.Body.Close()
			if err != nil {
				logger.Log("HTTP request execute fail", err)
				return nil, err
			}

			resBody, err := ioutil.ReadAll(resp.Body)
			if err != nil {
				logger.Log("HTTP response body read fail", err)
				return nil, err
			}
			json.Unmarshal(resBody, &rs)
		default:
			logger.Log("Function", paramFunc, "Execute", "fail")
			return nil, errors.New("No such Function")
		}

		logger.Log("Function", paramFunc, "Execute", "success")
		return rs, nil
	}, nil, nil
}

// JSONRequestBody JSON请求的报文体定义
type JSONRequestBody struct {
	Lang   string `json:"lang"`
	Target string `json:"target"`
}
